# module name heliodor.py
# $Id: heliodor.py ddcad87fab9c 2013/08/17 19:20:30 pgurumur $
# Copyright (c) 2012 Prabhu Gurumurthy <pgurumur@gmail.com>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
# File heliodor.py generated on Wednesday August 29, 2012 @ 23:20:02 PDT
#

__version__  = "$Revision: ddcad87fab9c $"
__author__   = "$Author: pgurumur $"
__modified__ = "$Date: 2013-08-17 19:20:30Z $"

import heapq
import itertools

from functools import wraps

from pyparsing import lineno

try:
   from . import exception, stdlib, node
except ImportError:
   import exception, stdlib, node

# bobjects = collections.OrderedDict()
builtins = set()
identifiers = set()
objects = {}

import heapq
import itertools

class Task(object):
   def __init__(self):
      super(Task, self).__init__()
      self._pq = []
      self._entry = {}
      self._counter = itertools.count()
      self._removed = "<>"

   def add(self, task, priority = 0):
      if task:
         if task in self._entry:
            self.remove(task)

         count = next(self._counter)
         entry = [priority, count, task]
         self._entry[task] = entry
         heapq.heappush(self._pq, entry)

   def remove(self, task):
      if task:
         entry = self._entry.pop(task)
         entry[-1] = self._removed

   def pop(self):
      retval = None
      while self._pq:
         priority, count, task = heapq.heappop(self._pq)
         if task is not self._removed:
            del self._entry[task]
            retval = task
            break

      if retval is None:
         raise KeyError("priority queue is empty")

      return retval

   def __add__(self, task):
      self.add(task, 0)
      return self

   def __sub__(self, task):
      self.remove(task)
      return self

   def __len__(self):
      return len(self._pq)

class BerylObject(object):
   def __init__(self, Tokens = None):
      self._function = set()
      self._namespace = set()
      self._variable = set()
      self._objects = {}

      self._task = Task()
      self._tokens = Tokens
      self._parent = None
      self._name = None

      self._context = None
      if Tokens and Tokens.getName():
         self._context = Tokens.getName()
      else:
         self._context = "main"

      self._lineno = 0

   def __str__(self):
      retval = None
      if self._name:
         retval = self._name
      else:
         retval = self._context

      return retval.format("ascii")

   def GetLineNumber(self):
      return self._lineno

   def SetLineNumber(self, Value):
      try:
         self._lineno = int(Value)
      except ValueError:
         self.error("INTERNAL ERROR, cannot coerce value to integer")

   def GetParent(self):
      return self._parent

   def SetParent(self, Object):
      if isinstance(Object, BerylObject):
         self._parent = Object
      else:
         self.error("cannot add non BerylObject object as parent")

   def error(self, fmt, *args):
      errmesg = None
      if len(args):
         errmesg = stdlib.sprintf(fmt, *args)
      else:
         errmesg = str(fmt)

      raise exception.CompileError(errmesg)

   def __call__(self, *args, **kwargs):
      raise NotImplementedError()

   @add2task
   def addq(self, Object, Line = 0):
      if Object:
         if Line > 0:
            self._task.add(Object, Line)
         else:
            self.error("Cannot add the object to run queue")
      else:
         self.error("cannot add null object to run queue")

   def popq(self):
      return self._task.pop()

   def addObjects(self, Object):
      if isinstance(Object, BerylNamespace):
         self._namespace.add(Object)
      elif isinstance(Object, BerylFunction):
         self._function.add(Object)
      elif isinstance(Object, BerylVariable):
         self._variable.add(Object)
      else:
         self.error("cannot add non BerylObject as a child")

      self.addq(Object, Object.line)

   def findvariable(self, varstring):
      retval = False
      if varstring and varstring in self._variables:
         retval = True

      return retval

   def __iadd__(self, Object):
      self.addObjects(Object)
      return self

   line = property(GetLineNumber, SetLineNumber)
   parent = property(GetParent, SetParent)
   context = property(fget = lambda self: self._context)
   objects = property(fget = lambda self: self._objects)
   tasklen = property(fget = lambda self: len(self._task))
   variables = property(fget = lambda self: self._variable)
   functions = property(fget = lambda self: self._function)
   namespaces = property(fget = lambda self: self._namespace)

class BerylNode(BerylObject):

class BerylString(BerylObject):


class BerylBoolOperand(object):
   symbol = None

   def __init__(self, tokens):
      super(BerylBoolOperand, self).__init__()
      self._args = tokens[0:2]

   def __str__(self):
      sep = " %s" %self.symbol
      return "(" + sep.join(map(str, self.args)) + ")"

class BerylAnd(BerylBoolOperand):
   symbol = "&"

   def __bool__(self):
      retval = False
      for ix in self.args:
         if isinstance(ix, (str, BerylString)):
            retval = eval(ix)
         else:
            retval = bool(ix)

         if retval:
            retval = True
            break

      return retval


def add2task(function):
   def wrapper(*args, **kwargs):
      bobject = args[1]
      mobject = args[0]
      if isinstance(bobject, BerylNamespace):
         mobject._namespace.add(bobject)
      elif isinstance(bobject, BerylFunction):
         mobject._function.add(bobject)
      elif isinstance(bobject, BerylVariable):
         mobject._variable.add(bobject)
      else:
         raise exception.CompileError("Cannot add non BerylObject as a child")

      function(*args, **kwargs)

   return wrapper

class BerylDecorate(object):
   def __init__(self, function):
      self.function = function
      self._object = None

   def __get__(self, obj, type = None):
      self._object = obj
      print("I am here", obj)
      return self.__class__(self.function.__get__(obj, type))

   def __call__(self, *args, **kwargs):
      bobject = self.function(*args)
      print(bobject)

class EvalObject(object):
   def __init__(self, tokens):
      self.value = tokens[0]

   def eval(self, variables):
      raise NotImplementedError()

   def operands(self, tlist):
      ix = iter(tlist)
      flag = True
      while flag:
         try:
            jy = next(ix)
            kz = next(ix)
            yield(jy, kz)
         except StopIteration:
            flag = False

class EvalConstant(EvalObject):
   def eval(self, variables):
      retval = None
      if self.value in variables:
         retval = variables[self.value]
      else:
         try:
            retval = int(self.value)
         except:
            retval = float(self.value)

      return retval

class EvalSignObject(EvalObject):
   def __init__(self, tokens):
      super(EvalSignObject, self).__init__(EvalObject)
      self.sign, self.value = tokens[0]
      self.mult = { '+': 1, '-': -1 }[self.sign]

   def eval(Self, variables):
      return self.mult * self.value.eval(variables)

class EvalMultObject(EvalObject):
   def eval(self, variables):
      prod = self.value[0].eval(variables)
      for op, val in self.operands(self.value[1:]):
         if op == '*':
            prod *= val.eval(variables)

         if op == '/':
            prod /= val.eval(variables)

         if op == '//':
            prod //= val.eval(variables)

         if op == '%':
            prod %= val.eval(variables)

      return prod

class EvalAddObject(EvalObject):
   def eval(self, variables):
      addsum = self.value[0].eval(variables)
      for op, val in self.operands(self.value[1:]):
         if op == '+':
            addsum += val.eval(variables)

         if op == '-':
            addsum -= val.eval(variables)

      return addsum

class EvalCompObject(EvalObject):
   opmap = {
         '<'   : lambda ix, jy: ix < jy,
         '<='  : lambda ix, jy: ix <= jy,
         '>'   : lambda ix, jy: ix > jy,
         '>='  : lambda ix, jy: ix >= jy,
         '=='  : lambda ix, jy: ix == jy,
         '!='  : lambda ix, jy: ix != jy,
         }

   def eval(self, variables):
      arg1 = self.value[0].eval(variables)
      for op, val in self.operands(self.value[1:]):
         func = self.opmap[op]
         arg2 = val.eval(variables)
         if not func(arg1, arg2):
            break

         arg1 = arg2

class EvalLogicObject(EvalObject):
   def eval(self, variables):
      retval = self.value[0].eval(variables)
      for op, val in self.operands(self.value[1:]):
         if op == '&':
            retval &= val.eval(variables)

         if op == '|':
            retval |= val.eval(variables)

         if op == '^':
            retval ^= val.eval(variables)

      return retval

class BerylNamespace(BerylObject):
   def __contains__(self, nsstring):
      retval = None
      for ix in self.namespaces:
         if ix.context == nsstring:
            retval = ix
            break

      return retval

   def __call__(self, *args, **kwargs):
      if self.tasklen:
         flag = True
         while flag:
            try:
               qobject = self.popq()
            except KeyError as err:
               flag = False
            else:
               try:
                  qobject(*args, **kwargs)
               except exception.CompileError as err:
                  self.error(err)
      else:
         self.error("No task added to the run queue")

class BerylFunction(BerylObject):
   def __init__(self, Tokens, Builtin = False):
      super(BerylFunction, self).__init__(Tokens)
      self._builtins = set()
      self._isbuiltin = False
      if Builtin:
         self._builtins.add(self)
         self._isbuiltin = True

      self._name = Tokens[0]

   def addObjects(self, Object):
      if isinstance(Object, (BerylVariable, BerylFunction)):
         super(BerylFunction, self).addObjects(Object)

   builtin = property(fget = lambda self: self._isbuiltin)

class BerylVariable(BerylObject):
   def __init__(self, Tokens, Name):
      super(BerylVariable, self).__init__(Tokens)

      if Name:
         self._name = Name

   def __contains__(self, varstring):
      retval = None
      for ix in self.parent.variables:
         if str(ix) == varstring:
            retval = ix
            break

      return retval

   def __call__(self, *args, **kwargs):
      if self._tokens[2] in self:
         temp = self.parent.objects[self._tokens[2]]
         newnode = node.create(temp.value, self._tokens[0], type(temp.value))
         if newnode:
            self.parent.objects[self._tokens[0]] = newnode
         else:
            self.error("INTERNAL ERROR, cannot create new node at line: %d",
                  self.line)
      else:
         if isinstance(self._tokens[2], node.Node):
            name = self._tokens[0]
            if self._tokens[1] == "=":
               self._tokens[2].name = name

            self.parent.objects[name] = self._tokens[2]
         else:
            self.error("Invalid variable %s at line %d", self._tokens[2],
                  self.line)

"""
   def validate(self, tokens):
      retval = None
      variable = tokens[0]
      if variable in identifiers:
         varnode = objects[variable]
         if isinstance(varnode, node.NodeString):
            index = start = end = -1
            if len(tokens) == 2:
               index = int(tokens[-1])
            elif len(tokens) == 3:
               (start, end) = tokens[1:]
            else:
               retval = varnode

            if start:
               start = int(start)

            if end:
               end = int(end)

            if index >= 0 and index < len(varnode.value):
               retval = node.NodeString(varnode.value[index])

            if start >= 0 and end >= 0 and end < len(varnode.value):
               retval = node.NodeString(varnode.value[start:end])

            if retval is None:
               self.error("Invalid index at line %d", self.line)
         elif isinstance(varnode, node.Node):
            retval = varnode
         else:
            self.error("INTERNAL ERROR, cannot determine the variable type")
      else:
         self.error("invalid variable %s at line %d", variable, self.line)

      return retval
"""

class BerylPrint(BerylFunction):
   def __init__(self, Tokens = None, Stdout = True):
      super(BerylPrint, self).__init__(Tokens, True)
      self._stdout = None
      self._vars = []
      self._nodes = []

      if Stdout:
         self._stdout = True
      else:
         self._stdout = False

      self._tokens = Tokens

   def _print(self, token):
      value = None
      if isinstance(token, node.Node):
         value = token.value
      elif token in self.parent.variables:
         varnode = self.parent.objects[token]
         value = varnode.value
      else:
         self.error("undefined variable %s at line %d", token, self.line)

      if value:
         print(value, "", end = "")

   def tovalue(self, tokens):
      for item in tokens:
         if item in self.parent.variables:
            varnode = self.parent.objects[item]
            self._vars.append(varnode.value)
         elif isinstance(item, node.Node):
            self._nodes.append(item.value)

   def _rprint(self, tokens):
      for item in tokens:
         self._print(item)

   def __call__(self, *args, **kwargs):
      if len(self._tokens) == 1:
         print()
      elif len(self._tokens) >= 3:
         nodetype = self._tokens[1]
         if isinstance(nodetype, node.NodeString):
            self.tovalue(self._tokens[1:])
            if self._vars:
               try:
                  mesg = stdlib.sprintf(nodetype.value, *self._vars)
                  print(mesg, end = "")
               except TypeError as err:
                  self._rprint(self._tokens[1:])
            elif self._nodes:
               self._rprint(self._tokens[1:])
         else:
            self._rprint(self._tokens[1:])

         print()
